package com.xd.core.system.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.xd.common.constant.SysConstant;
import com.xd.common.exception.EErrorCode;
import com.xd.common.exception.XkdException;
import com.xd.common.properties.PassWordKeyProperties;
import com.xd.common.smsLogin.H5MobilecodeAuthenticationToken;
import com.xd.common.smsLogin.MobilecodeAuthenticationToken;
import com.xd.common.util.*;
import com.xd.core.business.entity.IncomingInfo;
import com.xd.core.business.mapper.IncomingInfoMapper;
import com.xd.core.business.service.impl.IncomingInfoServiceImpl;
import com.xd.core.system.dto.LoginVo;
import com.xd.core.system.dto.ValidateH5TokenDto;
import com.xd.core.system.entity.*;
import com.xd.core.system.service.IH5SysUserService;
import com.xd.core.system.service.ISysPermissionService;
import com.xd.core.system.service.ISysUserService;
import com.xd.core.system.service.LoginService;
import com.xd.core.system.vo.SysUserVo;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {
    @Value("${aes.key}")
    private String aesKey;
    @Autowired
    private PassWordKeyProperties passWordKeyProperties;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Value("${image.localUrlPath}")
    private String localUrlPath;
    @Autowired
    private ISysPermissionService iSysPermissionService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private IncomingInfoMapper incomingInfoMapper;
    @Autowired
    private IH5SysUserService ih5SysUserService;
    private static final String failPasswordCount = "failPasswordCount:";
    private static final String loginCodeError = "loginCodeError:";
    private static final String redisLogin = "login:";
    private static final String redisToken = "token:";
    private static final String H5redisLogin = "H5login:";
    private static final String H5redisToken = "H5token:";
    private static final String H5_LINK = "H5_LINK:";//H5链接



    @Override
    public Map<String, Object> login(LoginVo user, HttpServletRequest request) throws Exception {
        String password = user.getPassword();
        if (StringUtils.isEmpty(password)) {
            throw new XkdException("密码不能为空");
        }
        //查询用户账号是否已锁定
        Integer o = (Integer) redisTemplate.opsForValue().get(failPasswordCount + user.getPhone());
        if (o != null && o >= 5) {
            throw new XkdException("密码错误次数超过5次，请10分钟后再试");
        }
        //通过UsernamePasswordAuthenticationToken获取用户名和密码
        UsernamePasswordAuthenticationToken authenticationToken = null;

        authenticationToken = new UsernamePasswordAuthenticationToken(user.getPhone(), RSAUtils.privateDecrypt(user.getPassword(), passWordKeyProperties.getPrivateKey())); //前台密码加密，后台解密
//            authenticationToken = new UsernamePasswordAuthenticationToken(user.getPhone(),pw);

        //AuthenticationManager委托机制对authenticationToken 进行用户认证
        try {
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            //如果认证没有通过，给出对应的提示
            if (Objects.isNull(authenticate)) {
                throw new RuntimeException("登录失败");
            }
            //如果认证通过，使用user生成jwt  jwt存入ResponseResult 返回
            //如果认证通过，拿到这个当前登录用户信息
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            //获取当前用户的userid
            SysUser sysUser = loginUser.getUser();
            //分析是否是异地登陆
            String ipAddr = IpUtils.getClientIpAddr(request);
            String a = IpUtils.getCountryByIpOffline(ipAddr, this.getClass().getClassLoader().getResource("ip2region.xdb").getPath());
            String b = a.substring(0, a.lastIndexOf("|"));
            String regularLoginCity = b.substring(b.lastIndexOf("|") + 1);
            if (StringUtils.isNotBlank(sysUser.getRegularLoginCity())) {//第一次登陆
                if (!sysUser.getRegularLoginCity().equals(regularLoginCity)) {
                    throw new XkdException(EErrorCode.PLACE_OTHER_LOGIN_FAIL, "异地登陆，请发送验证码");
                }
            }
            //存入登录时间
            SysUser u2 = new SysUser();
            u2.setId(sysUser.getId());
            u2.setLoginTime(new Date());
            if (StringUtils.isBlank(sysUser.getRegularLoginCity())) {//第一次登陆
                u2.setRegularLoginCity(regularLoginCity);
            }
            return saveRedisToken(loginUser, u2);
        } catch (BadCredentialsException e) {
            //登录失败密码错误次数
            redisTemplate.opsForValue().set(failPasswordCount + user.getPhone(), o == null ? 1 : o + 1, 10, TimeUnit.MINUTES);
            throw new XkdException("密码错误");
        }catch (XkdException e){
            throw e;
        }catch (Exception e) {
            e.printStackTrace();
            throw new XkdException("登陆失败");
        }


    }

    @Override
    public Map<String, Object> allopatricLogin(LoginVo user, HttpServletRequest request) throws Exception {
        String code = user.getCode().trim();
        String password = user.getPassword();
        String phone = user.getPhone();
        if (StringUtils.isEmpty(password)) {
            throw new XkdException("密码不能为空");
        }
        //查询用户账号是否已锁定
        Integer o = (Integer) redisTemplate.opsForValue().get(failPasswordCount + phone);
        if (o != null && o >= 5) {
            throw new XkdException("密码错误次数超过5次，请10分钟后再试");
        }
        String redisCode = (String) redisTemplate.opsForValue().get("loginCode:" + phone);
        if (StringUtils.isEmpty(redisCode)) {
            throw new XkdException("验证码失效");
        }
        if (!code.equals(redisCode)) {
            //判断验证码如果5分钟内错误5次则删除验证码
            Integer errorCount = (Integer) redisTemplate.opsForValue().get(loginCodeError+phone);
            if (errorCount == null){
                redisTemplate.opsForValue().set(loginCodeError+phone,1,5, TimeUnit.MINUTES);
            }else {
                if (errorCount >= 5){
                    redisTemplate.delete("loginCode:"+phone);
                    redisTemplate.delete(loginCodeError+phone);
                }else {
                    redisTemplate.opsForValue().increment(loginCodeError+phone,1);
                }
            }
            throw new XkdException("验证码错误");
        }

        //通过UsernamePasswordAuthenticationToken获取用户名和密码
        UsernamePasswordAuthenticationToken authenticationToken = null;
        authenticationToken = new UsernamePasswordAuthenticationToken(user.getPhone(), RSAUtils.privateDecrypt(user.getPassword(), passWordKeyProperties.getPrivateKey())); //前台密码加密，后台解密

        try {
            //AuthenticationManager委托机制对authenticationToken 进行用户认证
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);

            //如果认证没有通过，给出对应的提示
            if (Objects.isNull(authenticate)) {
                throw new RuntimeException("登录失败");
            }
            //如果认证通过，使用user生成jwt  jwt存入ResponseResult 返回

            //如果认证通过，拿到这个当前登录用户信息
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

            //获取当前用户的userid
            SysUser sysUser = loginUser.getUser();
            //分析是否是异地登陆
            String ipAddr = IpUtils.getClientIpAddr(request);
            String a = IpUtils.getCountryByIpOffline(ipAddr, this.getClass().getClassLoader().getResource("ip2region.xdb").getPath());
            String b = a.substring(0, a.lastIndexOf("|"));
            String regularLoginCity = b.substring(b.lastIndexOf("|") + 1);
            //存入登录时间
            SysUser u2 = new SysUser();
            u2.setId(sysUser.getId());
            u2.setLoginTime(new Date());
            u2.setRegularLoginCity(regularLoginCity);
            return saveRedisToken(loginUser, u2);
        }catch (BadCredentialsException e){
            //登录失败密码错误次数
            redisTemplate.opsForValue().set(failPasswordCount + user.getPhone(), o == null ? 1 : o + 1, 10, TimeUnit.MINUTES);
            throw new XkdException("密码错误");
        }catch (XkdException e){
            throw e;
        }catch (Exception e){
            throw new XkdException("登陆失败:"+e.getMessage());
        }

    }

    @Override
    public Map<String, Object> smslogin(LoginVo user, HttpServletRequest request) throws Exception {
        //验证短信
        MobilecodeAuthenticationToken authenticationToken = null;
        authenticationToken = new MobilecodeAuthenticationToken(user.getPhone(), user.getCode()); //前台密码加密，后台解密
        //AuthenticationManager委托机制对authenticationToken 进行用户认证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //如果认证没有通过，给出对应的提示
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败");
        }
        //如果认证通过，使用user生成jwt  jwt存入ResponseResult 返回
        //如果认证通过，拿到这个当前登录用户信息
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

        //获取当前用户的userid
        SysUser sysUser = loginUser.getUser();
        //分析是否是异地登陆
        String ipAddr = IpUtils.getClientIpAddr(request);
        String a = IpUtils.getCountryByIpOffline(ipAddr, this.getClass().getClassLoader().getResource("ip2region.xdb").getPath());
        String b = a.substring(0, a.lastIndexOf("|"));
        String regularLoginCity = b.substring(b.lastIndexOf("|") + 1);
//        if (StringUtils.isNotBlank(sysUser.getRegularLoginCity())) {//第一次登陆
//            if (!sysUser.getRegularLoginCity().equals(regularLoginCity)) {
//                throw new XkdException(EErrorCode.PLACE_OTHER_LOGIN_FAIL, "异地登陆，请发送验证码");
//            }
//        }
        //存入登录时间
        SysUser u2 = new SysUser();
        u2.setId(sysUser.getId());
        u2.setLoginTime(new Date());
//        if (StringUtils.isBlank(sysUser.getRegularLoginCity())) {//第一次登陆
        u2.setRegularLoginCity(regularLoginCity);
//        }
        //把完整的用户信息存入redis  userid为key   用户信息为value
        //获取token存入redis
        return saveRedisToken(loginUser, u2);
    }

    @Override
    public Map<String, Object> h5Smslogin(LoginVo user, HttpServletRequest request) throws Exception {
        //验证短信
        H5MobilecodeAuthenticationToken authenticationToken = null;
        authenticationToken = new H5MobilecodeAuthenticationToken(user.getPhone(), user.getCode()); //前台密码加密，后台解密
        //AuthenticationManager委托机制对authenticationToken 进行用户认证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //如果认证没有通过，给出对应的提示
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败");
        }
        //如果认证通过，使用user生成jwt  jwt存入ResponseResult 返回
        //如果认证通过，拿到这个当前登录用户信息
        H5MyLoginUser loginUser = (H5MyLoginUser) authenticate.getPrincipal();

        //获取当前用户的userid
        H5SysUser h5SysUser = loginUser.getUser();
        //分析是否是异地登陆
//        String ipAddr = IpUtils.getClientIpAddr(request);
//        String a = IpUtils.getCountryByIpOffline(ipAddr, this.getClass().getClassLoader().getResource("ip2region.xdb").getPath());
//        String b = a.substring(0, a.lastIndexOf("|"));
//        String regularLoginCity = b.substring(b.lastIndexOf("|") + 1);
        //存入登录时间
        H5SysUser u2 = new H5SysUser();
        u2.setId(h5SysUser.getId());
        u2.setLoginTime(new Date());
//        u2.setRegularLoginCity(regularLoginCity);
        //把完整的用户信息存入redis  userid为key   用户信息为value
        //获取token存入redis
        return H5saveRedisToken(loginUser, u2);
    }

    /*
     * 验证H5 token
     * */
    @Override
    public void validateH5Token(ValidateH5TokenDto validateH5TokenDto) throws Exception {
        //验证是否链接过期
        String token = validateH5TokenDto.getToken();
        String redisToken = (String) redisTemplate.opsForValue().get(H5_LINK + token);
        if (redisToken == null)
            throw new XkdException("页面已过期");
        //解析token 用户是否存在
        Claims claims = JwtUtil.parseJWT(token);
        String subject = claims.getSubject();
        JSONObject jsonObject = JSONObject.parseObject(subject);
        Integer incomingId = jsonObject.getInteger("incomingId");
        IncomingInfo incomingInfo = incomingInfoMapper.selectById(incomingId);
        if (incomingInfo == null)
            throw new XkdException("进件不存在");
    }

    private Map<String, Object> H5saveRedisToken(H5MyLoginUser loginUser, H5SysUser u2) {
        String userid = loginUser.getUser().getId().toString();
        H5SysUser sysUser = loginUser.getUser();
        HashMap<String, Object> jwtToken = new HashMap();
        jwtToken.put("userid", userid);
        jwtToken.put("sysType", SysConstant.SYSTEM_TYPE_H5);//登陆类型
        String jsonString = JSONObject.toJSONString(jwtToken);
        String jwt = JwtUtil.createJWT(jsonString);
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        SysUserVo sysUserVo = new SysUserVo();
        BeanUtils.copyProperties(sysUser, sysUserVo);
        map.put("user", sysUserVo);
        //获取token存入redis
        redisTemplate.opsForValue().set(H5redisLogin + userid, loginUser, 60 * 2, TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(H5redisToken + userid, jwt, 60 * 2, TimeUnit.MINUTES);
        ih5SysUserService.updateById(u2);
        log.info(u2.getUserName()+"H5登陆成功");
        return map;
    }

    private Map<String, Object> saveRedisToken(LoginUser loginUser, SysUser u2) {
        String userid = loginUser.getUser().getId().toString();
        SysUser sysUser = loginUser.getUser();
        HashMap<String, Object> jwtToken = new HashMap();
        jwtToken.put("userid", userid);
        jwtToken.put("sysType", SysConstant.SYSTEM_TYPE_WEB);//登陆类型
        String jsonString = JSONObject.toJSONString(jwtToken);
        String jwt = JwtUtil.createJWT(jsonString);
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        SysUserVo sysUserVo = new SysUserVo();
        BeanUtils.copyProperties(sysUser, sysUserVo);
        List<SysPermission> permissionTreeByList = iSysPermissionService.getPermissionTreeByList(loginUser.getSysPermission());
        sysUserVo.setSysPermission(permissionTreeByList);
        sysUserVo.setRoleId(loginUser.getRole().getId());
        sysUserVo.setCapitalId(loginUser.getCompany().getCapitalId());
        if (sysUser.getCompanyType().equals(SysConstant.USER_COMPANY_TYPE_ONE)) {
            sysUserVo.setCompanyId(SysConstant.SUPER_COMPANY_ID);
        } else {
            sysUserVo.setCompanyId(loginUser.getCompany().getId());
            sysUserVo.setPrestoreNo(loginUser.getCompany().getPrestoreNo());
            sysUserVo.setPrestoreApplyStatus(loginUser.getCompany().getPrestoreApplyStatus());
            sysUserVo.setAuthStatus(loginUser.getCompany().getAuthStatus());
            sysUserVo.setCompanyName(loginUser.getCompany().getCompanyName());
        }
        map.put("user", sysUserVo);
        map.put("headUrl", localUrlPath);//文件访问地址
        //获取token存入redis
        redisTemplate.opsForValue().set(redisLogin + userid, loginUser, 60 * 2, TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(redisToken + userid, jwt, 60 * 2, TimeUnit.MINUTES);
        //登陆成功删除错误次数
        redisTemplate.delete(failPasswordCount + sysUser.getPhone());
        redisTemplate.delete(loginCodeError + sysUser.getPhone());

        iSysUserService.updateById(u2);
        log.info(loginUser.getRole().getName() + ":" + sysUser.getUserName() + " 登陆成功");
        return map;
    }

    @Override
    public void logout(HttpServletRequest request) {
        //从SecurityContextHolder中的userid
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication instanceof UsernamePasswordAuthenticationToken) {
            Object principal = authentication.getPrincipal();
            if (principal instanceof LoginUser) {
                Integer userId = ((LoginUser) principal).getUser().getId();
                redisTemplate.delete(redisLogin + userId);
                redisTemplate.delete(redisToken + userId);
                log.info("用户 {} 成功注销", userId);
            } else {
                log.warn("未能获取到有效的登录用户信息");
            }
        } else {
            log.warn("当前用户未登录或为匿名用户，无需注销");
        }
        SecurityContextHolder.clearContext();
    }


}
